// wbuffer -- stream buffer for code conversions
#pragma once
#ifndef _CVT_WBUFFER_
#define _CVT_WBUFFER_
#ifndef RC_INVOKED
#include <streambuf>
#include <xstring>

#pragma pack(push, _CRT_PACKING)
#pragma warning(push, _STL_WARNING_LEVEL)
#pragma warning(disable : _STL_DISABLED_WARNINGS)
_STL_DISABLE_CLANG_WARNINGS
#pragma push_macro("new")
#undef new

// Example: to convert from UCS to UTF-8 and write to cout
// wbuffer_convert<codecvt_utf8<wchar_t>> mybuf(cout.rdbuf()); // construct wide stream buffer object
// wostream mywcout(&mybuf); // construct wide ostream object
// mywcout << (wchar_t)0x80; // writes 0xc2 0x80


namespace stdext {
    namespace cvt {

        // CLASS TEMPLATE wbuffer_convert
        template <class _Codecvt, class _Elem = wchar_t, class _Traits = _STD char_traits<_Elem>>
        class wbuffer_convert
            : public _STD basic_streambuf<_Elem, _Traits> { // stream buffer associated with a codecvt facet
            enum _Mode { _Unused, _Wrote, _Need, _Got, _Eof };
            enum { _STRING_INC = 8 };

        public:
            using _Mysb        = _STD streambuf;
            using _Byte_traits = _STD char_traits<char>;

            using int_type   = typename _Traits::int_type;
            using pos_type   = typename _Traits::pos_type;
            using off_type   = typename _Traits::off_type;
            using state_type = typename _Traits::state_type;

            explicit wbuffer_convert(_Mysb* _Strbuf = nullptr)
                : _Pcvt(new _Codecvt), _Mystrbuf(_Strbuf), _Status(_Unused),
                  _Nback(0) { // construct with byte stream buffer pointer
                static state_type _State0;

                _State = _State0;
                _Loc   = _STD locale(_Loc, _Pcvt);
            }

            wbuffer_convert(_Mysb* _Strbuf, _Codecvt* _Pcvt_arg)
                : _Pcvt(_Pcvt_arg), _Mystrbuf(_Strbuf), _Status(_Unused),
                  _Nback(0) { // construct with byte stream buffer pointer and codecvt
                static state_type _State0;

                _State = _State0;
                _Loc   = _STD locale(_Loc, _Pcvt);
            }

            wbuffer_convert(_Mysb* _Strbuf, _Codecvt* _Pcvt_arg, state_type _State_arg)
                : _Pcvt(_Pcvt_arg), _Mystrbuf(_Strbuf), _Status(_Unused),
                  _Nback(0) { // construct with byte stream buffer pointer, codecvt, and state
                _State = _State_arg;
                _Loc   = _STD locale(_Loc, _Pcvt);
            }

            virtual ~wbuffer_convert() noexcept { // destroy the object
                char* _Buf = (char*) _Str.c_str();

                while (_Status == _Wrote) { // put any trailing homing shift
                    char* _Dest;

                    if (_Str.size() < _STRING_INC) {
                        _Str.assign(_STRING_INC, '\0');
                    }

                    switch (
                        _Pcvt->unshift(_State, _Buf, _Buf + _Str.size(), _Dest)) { // test result of homing conversion
                    case _Codecvt::ok:
                        _Status = _Unused; // homed successfully

                    case _Codecvt::partial: // fall through
                    { // put any generated bytes
                        ptrdiff_t _Count = _Dest - _Buf;
                        if (0 < _Count
                            && _Byte_traits::eq_int_type(_Byte_traits::eof(),
                                   (typename _Traits::int_type) _Mystrbuf->sputn(_Buf, (_STD streamsize) _Count))) {
                            return; // write failed
                        }

                        if (_Status == _Wrote && _Count == 0) {
                            _Str.append(_STRING_INC, '\0'); // try with more space
                        }

                        break;
                    }

                    case _Codecvt::noconv:
                        return; // nothing to do

                    default:
                        return; // conversion failed
                    }
                }
            }

            _NODISCARD _Mysb* rdbuf() const { // return byte stream buffer pointer
                return _Mystrbuf;
            }

            _Mysb* rdbuf(_Mysb* _Strbuf) { // set byte stream buffer pointer
                _Mysb* _Oldstrbuf = _Mystrbuf;
                _Mystrbuf         = _Strbuf;
                return _Oldstrbuf;
            }

            _NODISCARD state_type state() const { // get state
                return _State;
            }

            wbuffer_convert(const wbuffer_convert&) = delete;
            wbuffer_convert& operator=(const wbuffer_convert&) = delete;

        protected:
            virtual int_type overflow(int_type _Meta = _Traits::eof()) { // put an element to stream
                if (_Traits::eq_int_type(_Traits::eof(), _Meta)) {
                    return _Traits::not_eof(_Meta); // EOF, return success code
                } else if (_Mystrbuf == nullptr || 0 < _Nback || (_Status != _Unused && _Status != _Wrote)) {
                    return _Traits::eof(); // no buffer or reading, fail
                } else { // put using codecvt facet
                    char* _Buf      = (char*) _Str.c_str();
                    const _Elem _Ch = _Traits::to_char_type(_Meta);
                    const _Elem* _Src;
                    char* _Dest;

                    if (_Str.size() < _STRING_INC) {
                        _Str.assign(_STRING_INC, '\0');
                    }

                    for (_Status = _Wrote;;) {
                        switch (_Pcvt->out(_State, &_Ch, &_Ch + 1, _Src, _Buf, _Buf + _Str.size(),
                            _Dest)) { // test result of converting one element
                        case _Codecvt::partial:
                        case _Codecvt::ok: { // converted something, try to put it out
                            ptrdiff_t _Count = _Dest - _Buf;
                            if (0 < _Count
                                && _Byte_traits::eq_int_type(_Byte_traits::eof(),
                                       (typename _Traits::int_type) _Mystrbuf->sputn(_Buf, (_STD streamsize) _Count))) {
                                return _Traits::eof(); // write failed
                            }

                            if (_Src != &_Ch) {
                                return _Meta; // converted whole element
                            }

                            if (0 >= _Count) {
                                if (_Str.size() >= 4 * _STRING_INC) {
                                    return _Traits::eof(); // conversion failed
                                }

                                _Str.append(_STRING_INC, '\0'); // try with more space
                            }
                            break;
                        }

                        case _Codecvt::noconv:
                            if (_Traits::eq_int_type(
                                    _Traits::eof(), (typename _Traits::int_type) _Mystrbuf->sputn(
                                                        (char*) &_Ch, (_STD streamsize) sizeof(_Elem)))) {
                                return _Traits::eof();
                            } else {
                                return _Meta; // put native byte order
                            }

                        default:
                            return _Traits::eof(); // conversion failed
                        }
                    }
                }
            }

            virtual int_type pbackfail(int_type _Meta = _Traits::eof()) { // put an element back to stream
                if (sizeof(_Myback) / sizeof(_Myback[0]) <= _Nback || _Status == _Wrote) {
                    return _Traits::eof(); // nowhere to put back
                } else { // enough room, put it back
                    if (!_Traits::eq_int_type(_Traits::eof(), _Meta)) {
                        _Myback[_Nback] = _Traits::to_char_type(_Meta);
                    }

                    ++_Nback;
                    if (_Status == _Unused) {
                        _Status = _Got;
                    }

                    return _Meta;
                }
            }

            virtual int_type underflow() { // get an element from stream, but don't point past it
                int_type _Meta;

                if (0 >= _Nback) {
                    if (_Traits::eq_int_type(_Traits::eof(), _Meta = _Get_elem())) {
                        return _Meta; // _Get_elem failed, return EOF
                    }

                    _Myback[_Nback++] = _Traits::to_char_type(_Meta);
                }

                return _Traits::to_int_type(_Myback[_Nback - 1]);
            }

#pragma warning(push)
#pragma warning(disable : 6385) // Reading invalid data from 'this->_Myback':
                                // the readable size is 'X' bytes, but 'Y' bytes may be read.
            virtual int_type uflow() { // get an element from stream, point past it
                int_type _Meta;

                if (0 >= _Nback) {
                    if (_Traits::eq_int_type(_Traits::eof(), _Meta = _Get_elem())) {
                        return _Meta; // _Get_elem failed, return EOF
                    }

                    _Myback[_Nback++] = _Traits::to_char_type(_Meta);
                }

                return _Traits::to_int_type(_Myback[--_Nback]);
            }
#pragma warning(pop)

            virtual pos_type seekoff(off_type, _STD ios_base::seekdir,
                _STD ios_base::openmode = (_STD ios_base::openmode)(
                    _STD ios_base::in | _STD ios_base::out)) { // change position by _Off
                return pos_type(-1); // always fail
            }

            virtual pos_type seekpos(pos_type,
                _STD ios_base::openmode = (_STD ios_base::openmode)(
                    _STD ios_base::in | _STD ios_base::out)) { // change position to _Pos
                return pos_type(-1); // always fail
            }

        private:
            int_type _Get_elem() { // compose an element from byte stream buffer
                if (_Mystrbuf != nullptr
                    && _Status != _Wrote) { // got buffer, haven't written, try to compose an element
                    if (_Status != _Eof) {
                        if (_Str.size() == 0) {
                            _Status = _Need;
                        } else {
                            _Status = _Got;
                        }
                    }

                    while (_Status != _Eof) { // get using codecvt facet
                        char* _Buf = (char*) _Str.c_str();
                        _Elem _Ch, *_Dest;
                        const char* _Src;
                        int _Meta;

                        if (_Status == _Need) {
                            if (_Byte_traits::eq_int_type(_Byte_traits::eof(), _Meta = _Mystrbuf->sbumpc())) {
                                _Status = _Eof;
                            } else {
                                _Str.push_back(_Byte_traits::to_char_type(_Meta));
                            }
                        }

                        switch (_Pcvt->in(_State, _Buf, _Buf + _Str.size(), _Src, &_Ch, &_Ch + 1,
                            _Dest)) { // test result of converting one element
                        case _Codecvt::partial:
                        case _Codecvt::ok:
                            _Str.erase((size_t) 0, // discard any used input
                                (size_t)(_Src - _Buf));
                            if (_Dest != &_Ch) {
                                return _Traits::to_int_type(_Ch);
                            }

                            break;

                        case _Codecvt::noconv:
                            if (_Str.size() < sizeof(_Elem)) {
                                break; // no conversion, but need more chars
                            }

                            _CSTD memcpy(&_Ch, _Buf,
                                sizeof(_Elem)); // copy raw bytes to element
                            _Str.erase((size_t) 0, sizeof(_Elem));
                            return _Traits::to_int_type(_Ch); // return result

                        default:
                            _Status = _Eof; // conversion failed
                        }
                    }
                }

                return _Traits::eof();
            }

            state_type _State; // code conversion state
            _Codecvt* _Pcvt; // the codecvt facet
            _Mysb* _Mystrbuf; // pointer to stream buffer
            _Mode _Status; // buffer read/write status
            size_t _Nback; // number of elements in putback buffer
            _Elem _Myback[8]; // putback buffer
            _STD string _Str; // unconsumed input bytes
            _STD locale _Loc; // manages reference to codecvt facet
        };
    } // namespace cvt
} // namespace stdext
#pragma pop_macro("new")
_STL_RESTORE_CLANG_WARNINGS
#pragma warning(pop)
#pragma pack(pop)

#endif // RC_INVOKED
#endif // _CVT_WBUFFER_

/*
 * Copyright (c) by P.J. Plauger. All rights reserved.
 * Consult your license regarding permissions and restrictions.
V6.50:0009 */
